﻿/*   
    Copyright (C) 2009 Galaktika Corporation ZAO

    This file is a part of Galaktika.BusinessMonitor
 
    Galaktika.BusinessMonitor is a free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.
      
    You should have received a copy of the GNU General Public License
    along with Galaktika.BusinessMonitor.  If not, see <http://www.gnu.org/licenses/>.
  
    If GPL v.3 is not suitable for your products or company,
    Galaktika Corp provides Galaktika.BusinessMonitor under a flexible commercial license
    designed to meet your specific usage and distribution requirements.
    If you have already obtained a commercial license from Galaktika Corp,
    you can use this file under those license terms.
*/

using System;
using System.Collections.Generic;
using System.Net;
using System.Text;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using Galaktika.BI.Runtime.Services;
using System.Windows.Threading;
using Galaktika.BI.Silverlight.ClientApp.Items;
using Galaktika.BI.Silverlight.Controls;

namespace Galaktika.BI.Silverlight.ClientApp.Services
{
    public class UserTaskManager : IUserTaskManager
    {
        #region WorkItemCache
        private class WorkItemCache
        {
            const int CACHE_MAX_SIZE = 10;

            Queue<UserTaskDescriptor> m_Cache = new Queue<UserTaskDescriptor>();
            public WorkItemCache()
            {
            }

            public UserTaskDescriptor Append(string caption)
            {
                if (m_Cache.Count > CACHE_MAX_SIZE)
                {
                    m_Cache.Dequeue();
                }
                UserTaskDescriptor wi = new UserTaskDescriptor(caption);
                //UserTaskDescriptor wi = new UserTaskDescriptor(caption, (App.CurrentApp.Root.Navigator as PageNavigator).SelectedPage);
                m_Cache.Enqueue(wi);

                return wi;
            }

            public string GetCurrentStatus()
            {
                foreach (var wi in m_Cache)
                {
                    if (wi.CurrentState == WorkItemState.Running)
                    {
                        return wi.Caption;
                    }
                }

                return Localization.AppTask_Done;
            }


            public override string ToString()
            {
                StringBuilder sb = new StringBuilder();
                foreach (UserTaskDescriptor wi in m_Cache)
                {
                    sb.AppendLine(wi.ToString());
                }

                return sb.ToString();
            }
        }
        #endregion

        TextBlock m_TxtStatus;
        WorkItemCache m_WorkItemCache;
        UserTaskPanel m_PnlTasks;
        //IServiceProvider m_Services;
        public UserTaskManager(UserTaskPanel pnlTasks, TextBlock txtStatus)
        {
            m_PnlTasks = pnlTasks;
            m_TxtStatus = txtStatus;
            //m_Services = services;
            this.Dispatcher = txtStatus.Dispatcher;
            m_WorkItemCache = new WorkItemCache();
        }

        public Dispatcher Dispatcher { get; private set; }

        public UserTaskDescriptor CreateTask(string caption)
        {
            UserTaskDescriptor ti = null;
            lock (m_WorkItemCache)
            {
                ti = m_WorkItemCache.Append(caption);
            }
            this.Dispatcher.BeginInvoke(() =>
                {
                    m_TxtStatus.Text = caption;
                    ToolTipService.SetToolTip(m_TxtStatus, null);
                    ToolTipService.SetToolTip(m_TxtStatus, new StyledToolTip() { Content = m_WorkItemCache });
                    if (m_PnlTasks != null)
                    {
                        m_PnlTasks.AppendTask(ti);
                    }
                });
            return ti;
        }

        public void DoneTask(UserTaskDescriptor wi)
        {
            this.Dispatcher.BeginInvoke(() =>
                {
                    wi.FinishMessage = Localization.AppTask_Done;
                    wi.Finished = DateTime.Now;
                    ToolTipService.SetToolTip(m_TxtStatus, null);
                    ToolTipService.SetToolTip(m_TxtStatus, new StyledToolTip() { Content = m_WorkItemCache });
                    if (m_PnlTasks != null)
                    {
                        m_PnlTasks.RefreshTasks();
                    }
                    m_TxtStatus.Text = m_WorkItemCache.GetCurrentStatus();
                });
        }

        public void AbortTask(UserTaskDescriptor wi, string finishMessage)
        {
            this.Dispatcher.BeginInvoke(() =>
                {
                    wi.FinishMessage = finishMessage;
                    wi.Finished = DateTime.Now;
                    ToolTipService.SetToolTip(m_TxtStatus, null);
                    ToolTipService.SetToolTip(m_TxtStatus, new StyledToolTip() { Content = m_WorkItemCache });
                    if (m_PnlTasks != null)
                    {
                        m_PnlTasks.RefreshTasks();
                    }
                    m_TxtStatus.Text = m_WorkItemCache.GetCurrentStatus();
                });
        }

        public void AbortTask(UserTaskDescriptor wi, Exception exc)
        {
            if (wi.GetErrorMessage != null)
            {
                Exception err = null;
                try
                {
                    err = new TaskExecutionException(wi.GetErrorMessage(), exc);
                }
                catch (Exception e)
                {
                    err = new TaskExecutionException(e.ToString(), exc);
                }
                this.LogExceptionToOutput(err);
            }
            else
            {
                this.LogExceptionToOutput(exc);
            }
            wi.Exception = exc;
            AbortTask(wi, exc.Message);
        }

        public void RunTaskAsync(string caption, WaitCallback callback)
        {
            RunTaskAsync(caption, callback, null);
        }

        public void RunTaskAsync(string caption, WaitCallback callback, object state)
        {
            this.RunTaskChainAsync(new UserTaskItem[] { new UserTaskItem(caption, callback, state) });
        }

        private static Dictionary<string, StringBuilder> m_PageErrors = new Dictionary<string, StringBuilder>();

        public void RunTaskChainAsync(IEnumerable<UserTaskItem> taskChain)
        {
            // если уже работаем в фоновом потоке 
            // то выполняем работы синхронно
            if (UserTaskThreadPool.IsPooled(Thread.CurrentThread))
            {
                foreach (var task in taskChain)
                {
                    task.Callback.Invoke(task.State);
                }

                return;
            }

            string formId = string.Empty;
            string formName = string.Empty;
            // получаем уникальный Id формы.
            // используем его для определения факта завершения всех работ для этой формы.
            if (App.CurrentApp.Root.Navigator is PageNavigator)
            {
                var form = ((PageNavigator)App.CurrentApp.Root.Navigator).SelectedHostForm;
                if (form != null)
                {
                    formId = form.UniqueId;
                    formName = form.Container.Title;
                }
            }

            // все дескрипторы должны быть установлены установлены в состояние "занят"
            // до добавления в пул

            var desc = new Dictionary<UserTaskDescriptor, UserTaskItem>();
            foreach (UserTaskItem task in taskChain)
            {
                var d = CreateTask(task.Caption);
                d.FormId = formName;
                desc.Add(d, task);
            }

            TabPage page = null;
            if (App.CurrentApp.Root.Navigator is PageNavigator)
            {
                page = ((PageNavigator)App.CurrentApp.Root.Navigator).SelectedPage;
            }
            if (page != null)
                Dispatcher.BeginInvoke(() =>
                {
                    page.IndicatorLoadingVisibility = Visibility.Visible;
                });

            UserTaskThreadPool.AddWork(
                p =>
                {
                    var descriptors = (Dictionary<UserTaskDescriptor, UserTaskItem>)p;
                    foreach (KeyValuePair<UserTaskDescriptor, UserTaskItem> kv in descriptors)
                    {
                        UserTaskDescriptor wi = kv.Key;
                        UserTaskItem task = kv.Value;
                        try
                        {
                            task.Callback.Invoke(task.State);
                            if (wi.Exception == null)
                            {
                                DoneTask(wi);
                            }
                            else
                            {
                                AbortTask(wi, wi.Exception);

                                AppendError(formId, wi.Exception);
                            }
                        }
                        catch (Exception exc)
                        {
                            AbortTask(wi, exc);

                            AppendError(formId, exc);

                            break;
                        }
                    }

                    try
                    {
                        if (page != null)
                        {
                            Monitor.Enter(page);
                        }

                        if (UserTaskThreadPool.IsEmptyCurrentPage(formId))
                        {
                            StringBuilder errors = RetrieveErrors(formId);
                            Dispatcher.BeginInvoke(() =>
                            {
                                if (page != null)
                                {
                                    page.IndicatorLoadingVisibility = Visibility.Collapsed;
                                    if (errors != null)
                                    {
                                        page.SetError(errors);
                                    }
                                }
                            });
                        }
                    }
                    finally
                    {
                        if (page != null)
                        {
                            Monitor.Exit(page);
                        }
                    }
                },
                desc, formId);
        }

        static void AppendError(string formId, Exception exc)
        {
            lock (m_PageErrors)
            {
                StringBuilder sb;
                m_PageErrors.TryGetValue(formId, out sb);
                if (sb == null)
                {
                    sb = new StringBuilder();
                    m_PageErrors.Add(formId, sb);
                }

                if (sb.Length > 0)
                {
                    sb.Append(Environment.NewLine);
                }
                sb.Append(exc.Message);
            }
        }

        static StringBuilder RetrieveErrors(string formId)
        {
            StringBuilder errors = null;
            lock (m_PageErrors)
            {
                if (m_PageErrors.ContainsKey(formId))
                {
                    errors = m_PageErrors[formId];
                    m_PageErrors.Remove(formId);
                }
            }

            return errors;
        }

        static object syncRoot = new object();
        public void RunTask(string caption, WaitCallback callback)
        {
            RunTask(caption, callback, null);
        }

        public void RunTask(string caption, WaitCallback callback, object state)
        {
            UserTaskDescriptor wi = CreateTask(caption);
            try
            {
                callback.Invoke(state);
                if (wi.Exception == null)
                {
                    DoneTask(wi);
                }
                else
                {
                    AbortTask(wi, wi.Exception);
                }
            }
            catch (Exception exc)
            {
                AbortTask(wi, exc);
            }
        }

        private void LogExceptionToOutput(Exception exc)
        {
            IOutputService output = App.CurrentApp.GetService<IOutputService>();
            if (output != null)
            {
                output.WriteException(exc);
            }
        }

        private void LogStringToOutput(string message)
        {
            var output = App.CurrentApp.GetService<IOutputService>();
            if (output != null)
            {
                output.WriteLine(message);
            }
        }
    }
}
